﻿/**
 * 此类用于管理各 Markdown 文件中的“锚”文本片段，以便编译 Html 时生成对这些锚的引用链接。
 * 
 * 各文本中“锚”片段形如：  [锚字面值 01](@锚ID 锚字面值 02)
 * 在保存 Markdown 文件时，它相对于工作区的路径（全小写）及其中所有的“锚”文本片段都会写入
 * 
 * Globals.PathOfWorkspaceAnchorsFilePath 这个文本文件末尾（不会考虑之前是否已在文件中写过旧的版本）。
 * 此类在读入时，会用文件尾部的内容覆盖之前同路径文件中的锚片段。
 * */

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    /// <summary>
    /// 切换工作区时，清空并重新载入相关的“锚”信息。
    /// </summary>
    public class AnchorsManager
    {
        private static List<AnchorWrap> anchorWraps = new List<AnchorWrap>();

        public static bool HasAnchor
        {
            get
            {
                if (anchorWraps.Count <= 0) return false;
                foreach (var aw in anchorWraps)
                {
                    if (aw.Anchors.Count > 0) return true;
                }

                return false;
            }
        }

        /// <summary>
        /// 适用于之前已经保存的 Markdown 文件。
        /// 当时保存文件时不会自动刷新 Anchor 信息到 Globals.PathOfWorkspaceAnchorsFilePath 文件中。
        /// </summary>
        public static void RebuildAnchorsFromWorkspaceFiles()
        {
            var markdownFileInfos = Globals.MainWindow.WorkspaceManager.MarkdownFileInfos;
            if (markdownFileInfos.Count <= 0) return;

            try
            {
                var reg = new Regex(@"\[.*\]\(@.*\)");

                StringBuilder sb1 = new StringBuilder();

                foreach (var mi in markdownFileInfos)
                {
                    if (File.Exists(mi.FullPath) == false) continue;
                    StringBuilder sb2 = new StringBuilder();
                    using (var fr = new StreamReader(mi.FullPath, Encoding.UTF8))
                    {
                        var line = fr.ReadLine();
                        bool hasAnchors = true;
                        while (string.IsNullOrWhiteSpace(line))
                        {
                            var matches = reg.Matches(line);
                            if (matches.Count <= 0)
                            {
                                hasAnchors = false;
                                return;
                            }

                            foreach (Match m in matches)
                            {
                                sb2.Append("\t" + m.Value + "\r\n");
                            }
                            line = fr.ReadLine();
                        }

                        if (hasAnchors)
                        {
                            sb1.Append(mi.TailPath + "\r\n");
                            sb1.Append(sb2);
                            sb1.Append("\r\n");
                        }
                    }
                }

                using (var fs = new FileStream(Globals.PathOfWorkspaceAnchorsFilePath, FileMode.Create))
                {
                    using (var sw = new StreamWriter(fs, Encoding.UTF8))
                    {
                        sw.Write(sb1.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// Load 之前，会自动清空已有的内容。
        /// </summary>
        public static void Load()
        {
            anchorWraps.Clear();

            if (File.Exists(Globals.PathOfWorkspaceAnchorsFilePath) == false) return;

            var trimChars = new char[] { ' ', '　', '\t' };

            using (var fr = new StreamReader(Globals.PathOfWorkspaceAnchorsFilePath, Encoding.UTF8))
            {
                var line = fr.ReadLine();
                AnchorWrap wrap = null;
                while (line != null)
                {
                    if (line.Trim() == "")
                    {
                        if (wrap != null)
                        {
                            Add(wrap);
                        }

                        wrap = null;  //准备创建一个新实例
                        line = fr.ReadLine();
                        continue;
                    }

                    if (line.StartsWith("\t"))  // 锚文本
                    {
                        if (wrap == null)
                        {
                            line = fr.ReadLine();
                            continue;
                        }

                        var text = line.Trim(trimChars);
                        var newAnchor = new Anchor(wrap, text);
                        wrap.Add(newAnchor);
                        // 这个方法会在添加前自动判断是否存在同样的 AnchorWrap。
                        // 从而可以实现“新值覆盖旧值”这个目的。
                        line = fr.ReadLine();
                        continue;
                    }

                    //路径顶格书写，表示开始一个 Markdown 文件中的所有锚文本的读取
                    if (wrap != null)
                    {
                        Add(wrap);
                    }

                    wrap = new AnchorWrap(line);
                    line = fr.ReadLine();
                }
            }

            // 计算自动编号的 字面值01
            foreach (var anchorWrap in anchorWraps)
            {
                var number = 1;
                foreach (var anchor in anchorWrap.Anchors)
                {
                    if (anchor.IsAutoNumber)
                    {
                        anchor.AutoNumber = number;
                        anchor.AutoNumberText = $"[^{number}]";
                        number++;
                    }
                }
            }
        }

        /// <summary>
        /// 会自动覆盖已存在 AnchorWrap。
        /// </summary>
        /// <param name="wrap"></param>
        public static void Add(AnchorWrap wrap)
        {
            if (wrap == null || wrap.Anchors.Count <= 0) return;

            var oldWrap = FindSamePathWrap(wrap);
            if (oldWrap != null)
            {
                foreach (var a in wrap.Anchors)
                {
                    oldWrap.Add(a);
                }
            }
            else anchorWraps.Add(wrap);
        }

        private static AnchorWrap FindSamePathWrap(AnchorWrap wrap)
        {
            if (wrap == null) return null;
            if (anchorWraps.Count <= 0) return null;

            foreach (var aw in anchorWraps)
            {
                if (aw.TailPath.ToLower() == wrap.TailPath.ToLower()) return aw;
            }

            return null;
        }

        /// <summary>
        /// 此方法应在 Load() 方法之后调用，以去除重复的内容并重新保存。
        /// </summary>
        public static void Save()
        {
            if (anchorWraps.Count <= 0) return;
            try
            {
                StringBuilder sb = new StringBuilder();
                foreach (var aw in anchorWraps)
                {
                    foreach (var at in aw.Anchors)
                    {
                        sb.Append(aw.TailPath + "\r\n");
                        sb.Append("\t" + at.Text + "\r\n\r\n");
                    }
                }

                using (var fs = new FileStream(Globals.PathOfWorkspaceAnchorsFilePath, FileMode.Create))
                {
                    using (var sw = new StreamWriter(fs, Encoding.UTF8))
                    {
                        sw.Write(sb.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
            }
        }

        private enum AnchorMacthRule
        {
            All,    //默认情况下严格相等
            End,    //要查找的关键词以 . 结尾
            Start,  //要查找的关键词以 . 开头
            Center, //要查找的关系词以 . 开头并结尾
        }

        /// <summary>
        /// 判断 name 是否与“锚”的某个指定片段匹配。
        /// </summary>
        private static bool IsMatch(string name, AnchorMacthRule rule, string anchorSpan)
        {
            switch (rule)
            {
                case AnchorMacthRule.All:  // 严格相等（不区分大小写）
                    if (name.ToLower() == anchorSpan.ToLower()) return true;
                    break;
                case AnchorMacthRule.End:  // 尾部有通配符，只要头部相等即可。
                    if (anchorSpan.ToLower().StartsWith(name.ToLower())) return true;
                    break;
                case AnchorMacthRule.Start:  // 头部有通配符，只要尾部相等即可。
                    if (anchorSpan.ToLower().EndsWith(name.ToLower())) return true;
                    break;
                case AnchorMacthRule.Center:  // 两侧均有通配符，只要包含即可。
                    if (anchorSpan.ToLower().Contains(name.ToLower())) return true;
                    break;
                default:
                    break;
            }

            return false;
        }

        /// <summary>
        /// 根据提供的 name 来查找有没有可以链接的“锚”。如果没有，直接返回“脱壳”后的 Name。
        /// </summary>
        /// <param name="name">在 Markdown 文件中以 <: 和 :> 包围的文本片段。</param>
        /// <param name="srcFilePath">Markdown 文件的实际路径。</param>
        /// <returns>返回链接列表。</returns>
        internal static string BuilderLinks(string name, string srcFilePath)
        {
            if (string.IsNullOrWhiteSpace(name)) return name;

            var reg = new Regex(@"(?<=(\<[:：])).*?(?=([：:]\>))");
            var match = reg.Match(name);
            if (match.Success) name = match.Value.Trim(new char[] { ' ', '　', '\t' });
            if (string.IsNullOrWhiteSpace(name)) return name;
            // 为什么这样？因为设计目标是：
            // ①如果找到可以引用的链接，就用链接取代 <:xxx:> 这样的文本
            // ②如果找不到，就直接将 <:xxx:> 改成 xxx ——这样对编译出来的 Html 就不会产生任何影响！
            // 所以 <:  :>  是个“无副作用标记”。

            var rule = AnchorMacthRule.All;
            var trimChars = new char[] { ' ', '　', '\t' };
            if (name.StartsWith("."))
            {
                if (name.EndsWith("."))
                {
                    rule = AnchorMacthRule.Center;  // 首尾都是 . 只要包含即可。
                    name = name.Substring(1, name.Length - 2).Trim(trimChars);
                    if (string.IsNullOrWhiteSpace(name)) return "";
                }
                else
                {
                    rule = AnchorMacthRule.Start;  // 头部以 . 开始
                    name = name.Substring(1).Trim(trimChars);
                    if (string.IsNullOrWhiteSpace(name)) return "";
                }
            }
            else
            {
                if (name.EndsWith("."))
                {
                    rule = AnchorMacthRule.End;  // 尾部以 . 结尾
                    name = name.Substring(0, name.Length - 1).Trim(trimChars);
                    if (string.IsNullOrWhiteSpace(name)) return "";
                }
            }

            if (HasAnchor == false) { Load(); }

            bool onlyLinkInThisDocument = false;
            bool isSup = false;
            if (name.Length >= 1)
            {
                var fstChar = name[0];
                if (fstChar == '^' || fstChar == '-')  // - 号表示只在当前文档中尝试寻找可链接对象
                {
                    if (fstChar == '^') isSup = true;

                    name = name.Substring(1);
                    if (string.IsNullOrWhiteSpace(name)) return "";
                    onlyLinkInThisDocument = true;
                }
                else if (fstChar == '+')  // + 号是默认行为，可省略，表示在整个工作区中尝试寻找可链接的对象并创建链接列表。
                {
                    name = name.Substring(1);
                    if (string.IsNullOrWhiteSpace(name)) return "";
                    //onlyLinkInThisDocument = false;
                }
            }

            var aLinks = new List<string>();

            foreach (var aw in anchorWraps)
            {
                foreach (var at in aw.Anchors)
                {
                    if (onlyLinkInThisDocument)
                    {
                        var aFullPath = Globals.PathOfWorkspace + at.TailPath.ToLower();
                        if (aFullPath.ToLower() != srcFilePath.ToLower()) continue;
                        // 如果指定为脚注或用户明确指定只在当前文件中搜索，只搜索当前文件中的锚——这些锚应写在文件尾部。
                    }

                    //if (name.ToLower() == at.Word1.ToLower())
                    if (string.IsNullOrWhiteSpace(at.Word1) == false &&
                        at.IsAutoNumber == false &&  // 自动编号的锚形如：[[^2]](@ID "alt text")，不允许匹配 字面值01。
                        IsMatch(name, rule, at.Word1))
                    {
                        var linkPath = MarkdownEditorBase.GetRelativePath(
                            srcFilePath,
                            Globals.PathOfWorkspace + aw.TailPath,
                            true);
                        var alt = name;
                        if (string.IsNullOrEmpty(at.Word2) == false) { alt = at.Word2; if (alt.StartsWith("^")) alt = alt.Substring(1); }
                        else if (string.IsNullOrWhiteSpace(at.Word1) == false) { alt = at.Word1; }
                        var linkText = $"[{at.Word1}]({linkPath}#{at.ID} \"{alt}\")";
                        aLinks.Add(linkText);
                        continue;
                    }

                    //if (name.ToLower() == at.Word2.ToLower())
                    if (string.IsNullOrWhiteSpace(at.Word2) == false && IsMatch(name, rule, at.Word2))
                    {
                        var linkPath = MarkdownEditorBase.GetRelativePath(
                           srcFilePath,
                           Globals.PathOfWorkspace + aw.TailPath,
                           true);
                        var alt = name;
                        if (string.IsNullOrEmpty(at.Word2) == false) { alt = at.Word2; if (alt.StartsWith("^")) alt = alt.Substring(1); }
                        else if (string.IsNullOrWhiteSpace(at.Word1) == false) { alt = at.Word1; }
                        var linkText = $"[{(string.IsNullOrWhiteSpace(at.Word1) ? at.Word2 : at.Word1)}]({linkPath}#{at.ID} \"{alt}\")";
                        aLinks.Add(linkText);
                        continue;
                    }

                    //if (name.ToLower() == at.ID.ToLower())
                    if (string.IsNullOrWhiteSpace(at.ID) == false && IsMatch(name, rule, at.ID))
                    {
                        var linkPath = MarkdownEditorBase.GetRelativePath(
                           srcFilePath,
                           Globals.PathOfWorkspace + aw.TailPath,
                           true);
                        var alt = name;
                        if (string.IsNullOrEmpty(at.Word2) == false) { alt = at.Word2; if (alt.StartsWith("^")) alt = alt.Substring(1); }
                        else if (string.IsNullOrWhiteSpace(at.Word1) == false) { alt = at.Word1; }
                        var linkText = $"[{(string.IsNullOrWhiteSpace(at.Word1) ? (string.IsNullOrWhiteSpace(at.Word2) ? name : at.Word2) : at.Word1)}]({linkPath}#{at.ID} \"{alt}\")";
                        aLinks.Add(linkText);
                        continue;
                    }
                }
            }

            if (aLinks.Count > 0)
            {
                var sb = new StringBuilder();
                foreach (var alink in aLinks)
                {
                    sb.Append(alink);
                    sb.Append("、");
                }

                if (isSup)
                {
                    return $"<sup>{sb.ToString().Trim(new char[] { '、', })}</sup>";
                }
                else return sb.ToString().Trim(new char[] { '、', });
            }
            else return name;
        }
    }
}
